#pragma once


#include <queue>
#include <mutex>
#include <condition_variable>


namespace cybertron {

template <class T>
class SafeQueue
{
public:
	SafeQueue(size_t max = 0)
		: _max_capacity(max)
		, _queue()
		, _lock()
		, _not_empty()
		, _not_full() {
	}

	~SafeQueue(void) {
		std::lock_guard<std::mutex> lock(_lock);
		while (!is_empty()) {
			T *val = _queue.front();
			_queue.pop();
			if (val) {
				delete val;
			}
		}
	}

	void set_capacity(size_t max) {
		std::lock_guard<std::mutex> lock(_lock);
		_max_capacity = max;
	}

	// Add an element to the queue.
	bool enqueue(T *t, int timeoutms = 100)
	{
		std::unique_lock<std::mutex> lock(_lock);
		if (is_full()) {
			if (timeoutms < 0) {
				_not_full.wait(lock);
			}
			else if (timeoutms == 0) {
				return false;
			}
			else {
				_not_full.wait_for(lock, std::chrono::milliseconds(timeoutms));
			}
			if (is_full()) {
				return false;
			}
		}
		_queue.push(t);
		_not_empty.notify_one();

		return true;
	}

	// Get the "front"-element.
	// If the queue is empty, wait till a element is available.
	T *dequeue(int timeoutms = 100)
	{
		std::unique_lock<std::mutex> lock(_lock);
		if (is_empty()) {
			if (timeoutms < 0) {
				_not_empty.wait(lock);
			}
			else if (timeoutms == 0) {
				return nullptr;
			}
			else {
				_not_empty.wait_for(lock, std::chrono::milliseconds(timeoutms));
			}
			if (is_empty()) {
				return nullptr;
			}
		}
		T *val = _queue.front();
		_queue.pop();
		_not_full.notify_one();
		return val;
	}

	size_t size() {
		std::lock_guard<std::mutex> lock(_lock);
		return _queue.size();
	}

private:
	bool is_full() {
		return (_max_capacity > 0 && _queue.size() >= _max_capacity);
	}

	bool is_empty() {
		return _queue.empty();
	}

	size_t _max_capacity;
	std::queue<T*> _queue;
	mutable std::mutex _lock;
	std::condition_variable _not_empty;
	std::condition_variable _not_full;
};
}